import time
from pathlib import Path
from typing import Optional, Callable, List

from .config import Config
from .logger import log_event


class SpeechFeedback:
    def __init__(self):
        self._coqui_tts = None
        self._pyttsx3 = None
        self._winsound_available = False
        # speaking callbacks
        self._on_speak_start: List[Callable[[], None]] = []
        self._on_speak_end: List[Callable[[], None]] = []
        self._init_backends()

    def add_speak_start_listener(self, cb: Callable[[], None]) -> None:
        self._on_speak_start.append(cb)

    def add_speak_end_listener(self, cb: Callable[[], None]) -> None:
        self._on_speak_end.append(cb)

    def _notify_start(self) -> None:
        for cb in list(self._on_speak_start):
            try:
                cb()
            except Exception:
                pass

    def _notify_end(self) -> None:
        for cb in list(self._on_speak_end):
            try:
                cb()
            except Exception:
                pass

    def _init_backends(self):
        # Try Coqui TTS
        try:
            from TTS.api import TTS  # type: ignore
            self._coqui_tts = TTS(model_name=Config.TTS_MODEL)
        except Exception:
            self._coqui_tts = None
        # Try pyttsx3
        try:
            import pyttsx3  # type: ignore
            self._pyttsx3 = pyttsx3.init()
            # Optional: tune voice rate for Chinese
            self._pyttsx3.setProperty("rate", 170)
        except Exception:
            self._pyttsx3 = None
        # winsound is built-in on Windows for WAV playback
        try:
            import winsound  # type: ignore
            self._winsound_available = True
        except Exception:
            self._winsound_available = False

    def speak(self, text: str) -> Optional[Path]:
        text = text.strip()
        if not text:
            return None
        log_event("TTS_START", f"text={text[:60]}")

        # Prefer Coqui
        if self._coqui_tts is not None:
            out = Config.OUTPUT_DIR / f"feedback_{int(time.time())}.wav"
            try:
                self._coqui_tts.tts_to_file(text=text, file_path=str(out))
            except Exception:
                # If coqui fails at runtime, fall back to pyttsx3
                self._coqui_tts = None
            else:
                # Estimate duration from WAV
                duration = 2.0
                try:
                    import wave
                    with wave.open(str(out), "rb") as wf:
                        frames = wf.getnframes()
                        framerate = wf.getframerate()
                        if framerate > 0:
                            duration = max(0.5, frames / float(framerate))
                except Exception:
                    # fallback: rough estimate by text length
                    duration = min(max(len(text) / 6.0, 1.0), 6.0)
                # Notify speaking start
                self._notify_start()
                # Play if possible using winsound (Windows)
                if self._winsound_available:
                    try:
                        import winsound  # type: ignore
                        winsound.PlaySound(str(out), winsound.SND_FILENAME | winsound.SND_ASYNC)
                    except Exception:
                        pass
                # Schedule end notification
                try:
                    import threading
                    threading.Timer(duration, self._notify_end).start()
                except Exception:
                    pass
                log_event("TTS_END", f"text_len={len(text)}")
                return out

        # Fallback to pyttsx3 (Windows SAPI5)
        if self._pyttsx3 is not None:
            try:
                self._notify_start()
                self._pyttsx3.say(text)
                self._pyttsx3.runAndWait()
                self._notify_end()
                log_event("TTS_END", f"text_len={len(text)}")
                return None
            except Exception:
                return None

        return None

    def stop(self) -> None:
        """Best-effort stop of any ongoing speech output and notify end."""
        log_event("TTS_STOP", "stopping current speech")
        # Stop winsound playback if any
        try:
            import winsound
            winsound.PlaySound(None, winsound.SND_PURGE)
        except Exception:
            pass
        # Stop pyttsx3 queue if available
        if self._pyttsx3 is not None:
            try:
                self._pyttsx3.stop()
            except Exception:
                pass
        # Ensure UI resets mouth animation
        try:
            self._notify_end()
        except Exception:
            pass